package simple.utils.pool;

import com.google.common.base.Preconditions;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import simple.utils.cache.ByteArray;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;

/**
 * Created with IntelliJ IDEA.
 * User: rehak
 * Date: 25.4.13
 * Time: 14:03
 * To change this template use File | Settings | File Templates.
 */
public class MessageDigests {

    private static Cache<String, Pool<MessageDigest>> pools = CacheBuilder.newBuilder().build();
    private static Set<String> supportedAlgorithms = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
    private static Set<String> unsupportedAlgorithms = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());

    private MessageDigests() {
        //not constructable
    }

    public static boolean isAlgorithmSupported(String algorithm) {
        try {
            if (supportedAlgorithms.contains(algorithm)) {
                return true;
            } else if (unsupportedAlgorithms.contains(algorithm)) {
                return false;
            } else {
                MessageDigest instance = MessageDigest.getInstance(algorithm);
                instance = null;
                supportedAlgorithms.add(algorithm);
                return true;
            }
        } catch (NoSuchAlgorithmException e) {
            unsupportedAlgorithms.add(algorithm);
            return false;
        }
    }

    private static Callable<Pool<MessageDigest>> createPool(final String algo) {
        Preconditions.checkArgument(isAlgorithmSupported(algo));
        return new Callable<Pool<MessageDigest>>() {
            @Override
            public Pool<MessageDigest> call() throws Exception {
                Pool<MessageDigest> mdPool = new Pool<MessageDigest>() {
                    @Override
                    protected MessageDigest createInstance() {
                        try {
                            return MessageDigest.getInstance(algo);
                        } catch (NoSuchAlgorithmException e) {
                            throw new RuntimeException("Unable to get MD algorithm " + algo);
                        }
                    }

                    @Override
                    protected void cleanUp(MessageDigest instance) {
                        instance.reset();
                    }
                };
                return mdPool;
            }
        };
    }

    public static ByteArray getHash(String text, String algorithm) {
        Pool<MessageDigest> pool = getPool(algorithm);
        MessageDigest md = pool.getInstance();
        byte[] digest = new byte[0];
        try {
            digest = md.digest(text.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Unsupported UTF-8 encoding");
        }
        pool.returnInstance(md);
        return ByteArray.wrap(digest);
    }

    public static ByteArray getHash(byte[] data, String algorithm) {
        Pool<MessageDigest> pool = getPool(algorithm);
        MessageDigest md = pool.getInstance();
        byte[] digest = md.digest(data);
        pool.returnInstance(md);
        return ByteArray.wrap(digest);
    }


    public static Pool<MessageDigest> getPool(String algorithm) {
        if (!isAlgorithmSupported(algorithm)) {
            throw new IllegalArgumentException("Unsupported algorithm " + algorithm);
        }
        try {
            Pool<MessageDigest> p = pools.getIfPresent(algorithm);
            if (p == null) {
                Callable<Pool<MessageDigest>> pool = createPool(algorithm);
                return pools.get(algorithm, pool);
            } else {
                return p;
            }
        } catch (ExecutionException e) {
            throw new RuntimeException("Error creating pool!", e);
        }
    }

    public static ByteArray getHash(FileInputStream in, int bufferSize, String algorithm) throws IOException {
        Pool<MessageDigest> pool = getPool(algorithm);
        MessageDigest md = pool.getInstance();
        byte[] buf = new byte[bufferSize];
        int read;
        while ((read = in.read(buf)) > -1) {
            md.update(buf, 0, read);
        }
        byte[] digest = md.digest();
        pool.returnInstance(md);
        return ByteArray.wrap(digest);
    }
}
